package org.mozhu.mboot.core.json;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.Assert;

import java.io.*;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.List;
import java.util.Map;
import java.util.Set;

public class JSON {
    private static final Logger LOGGER = LoggerFactory.getLogger(JSON.class);
    private static JSONHandlerProvider JSON_HANDLER_PROVIDER;

    private static class JSONHolder {
        private static final JSON INSTANCE = new JSON();

        private JSONHolder() {
        }
    }

    public static final JSON get() {
        return JSONHolder.INSTANCE;
    }

    public static void setJsonHandlerProvider(JSONHandlerProvider jsonHandlerProvider) {
        JSON_HANDLER_PROVIDER = jsonHandlerProvider;
    }

    public static Object deserialize(Reader reader) throws JSONParseException, IOException {
        checkState();
        return JSON_HANDLER_PROVIDER.getJSONDeserializer().deserialize(reader);
    }

    public static <T> T deserialize(Reader reader, Class<T> valueType) throws JSONParseException, IOException {
        checkState();
        return JSON_HANDLER_PROVIDER.getJSONDeserializer().deserialize(reader, valueType);
    }

    public static <T> T deserialize(Reader reader, Type valueType) throws JSONParseException, IOException {
        checkState();
        return JSON_HANDLER_PROVIDER.getJSONDeserializer().deserialize(reader, valueType);
    }

    public static void serialize(Writer writer, Object object) throws IOException {
        checkState();
        JSON_HANDLER_PROVIDER.getJSONSerializer().serialize(writer, object);
    }

    public static void serializePretty(Writer writer, Object object) throws IOException {
        checkState();
        JSON_HANDLER_PROVIDER.getJSONSerializer().serializePretty(writer, object);
    }

    public static String toJson(Object object) {
        checkState();
        try {
            return JSON_HANDLER_PROVIDER.getJSONSerializer().toJSONString(object);
        } catch (IOException e) {
            LOGGER.warn("write to json string error:" + object, e);
            return null;
        }
    }

    public static String toPrettyJson(Object object) {
        checkState();
        try {
            return JSON_HANDLER_PROVIDER.getJSONSerializer().toPrettyJSONString(object);
        } catch (IOException e) {
            LOGGER.warn("write to json string error:" + object, e);
            return null;
        }
    }

    public static <T> T toJavaObject(String jsonString, Class<T> valueType) {
        if (jsonString == null || jsonString.length() == 0) {
            return null;
        }
        try {
            return deserialize(new StringReader(jsonString), valueType);
        } catch (Exception e) {
            LOGGER.warn("parse json string error:" + jsonString, e);
            throw new RuntimeException("invalid json value " + jsonString, e);
        }
    }

    public static <T> T toJavaObject(String jsonString, Type valueType) {
        if (jsonString == null || jsonString.length() == 0) {
            return null;
        }
        try {
            return deserialize(new StringReader(jsonString), valueType);
        } catch (Exception e) {
            LOGGER.warn("parse json string error:" + jsonString, e);
            throw new RuntimeException("invalid json value " + jsonString, e);
        }
    }

    public static <T> List<T> toList(String jsonString, Class<T> valueType) {
        if (jsonString == null || jsonString.length() == 0) {
            return null;
        }
        try {
            return deserialize(new StringReader(jsonString), SimpleParameterizedType.createCollectionType(List.class, valueType));
        } catch (Exception e) {
            LOGGER.warn("parse json string error:" + jsonString, e);
            throw new RuntimeException("invalid json value " + jsonString, e);
        }
    }

    public static <T> Set<T> toSet(String jsonString, Class<T> valueType) {
        if (jsonString == null || jsonString.length() == 0) {
            return null;
        }
        try {
            return deserialize(new StringReader(jsonString), SimpleParameterizedType.createCollectionType(Set.class, valueType));
        } catch (Exception e) {
            LOGGER.warn("parse json string error:" + jsonString, e);
            throw new RuntimeException("invalid json value " + jsonString, e);
        }
    }

    public static <K, V> Map<K, V> toMap(String jsonString, Class<K> keyType, Class<V> valueType) {
        if (jsonString == null || jsonString.length() == 0) {
            return null;
        }
        try {
            return deserialize(new StringReader(jsonString), SimpleParameterizedType.createMapType(Map.class, keyType, valueType));
        } catch (Exception e) {
            LOGGER.warn("parse json string error:" + jsonString, e);
            throw new RuntimeException("invalid json value " + jsonString, e);
        }
    }

    public static void format(Reader reader, Writer writer, boolean pretty) throws IOException, JSONParseException {
        if (pretty) {
            serializePretty(writer, deserialize(reader));
        } else {
            serialize(writer, deserialize(reader));
        }
    }

    public static String format(String string, boolean pretty) {
        StringReader stringReader = new StringReader(string);
        StringWriter stringWriter = new StringWriter();
        try {
            format(stringReader, stringWriter, pretty);
        } catch (IOException e) {
            // TODO ignore
            return null;
        } catch (JSONParseException e) {
            // TODO ignore
            return null;
        }
        return stringWriter.toString();
    }

    private static void checkState() {
        Assert.notNull(JSON_HANDLER_PROVIDER, "jsonHandlerProvider should not be null");
    }

    private JSON() {
    }

    @SuppressWarnings("rawtypes")
    public static class SimpleParameterizedType implements ParameterizedType {
        private Class ownerType;
        private Type[] argumentTypes;

        public static SimpleParameterizedType createCollectionType(Class collectionClass, Class itemType) {
            SimpleParameterizedType simpleParameterizedType = new SimpleParameterizedType();
            simpleParameterizedType.ownerType = collectionClass;
            simpleParameterizedType.argumentTypes = new Type[]{itemType};

            return simpleParameterizedType;
        }

        public static SimpleParameterizedType createMapType(Class mapClass, Class keyType, Class valueType) {
            SimpleParameterizedType simpleParameterizedType = new SimpleParameterizedType();
            simpleParameterizedType.ownerType = mapClass;
            simpleParameterizedType.argumentTypes = new Type[]{keyType, valueType};

            return simpleParameterizedType;
        }

        @Override
        public Type[] getActualTypeArguments() {
            return argumentTypes;
        }

        @Override
        public Type getRawType() {
            return ownerType;
        }

        @Override
        public Type getOwnerType() {
            return ownerType;
        }
    }
}
